page.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import { fetchJson } from '@/lib/utils/server';
  2. import { ResultDto } from '@/types/response/common';
  3. import { ChannelDetail } from '@/types/channel';
  4. import { notFound } from 'next/navigation';
  5. import Link from 'next/link';
  6. import './style.scss';
  7. type Props = {
  8. params: Promise<{ channelSID: string }>;
  9. };
  10. function formatCount(n: number): string {
  11. if (n >= 10000) return `${(n / 10000).toFixed(n >= 100000 ? 0 : 1)}만`;
  12. if (n >= 1000) return `${(n / 1000).toFixed(1)}천`;
  13. return n.toLocaleString();
  14. }
  15. export default async function ChannelPage({ params }: Props) {
  16. const { channelSID } = await params;
  17. const res: ResultDto<ChannelDetail> = await fetchJson(`/api/channel/${channelSID}`, { method: 'GET' });
  18. if (!res.data) {
  19. notFound();
  20. }
  21. const ch = res.data;
  22. return (
  23. <div className="channel-page">
  24. {/* 배너 */}
  25. <div className="channel-page__banner">
  26. {ch.bannerUrl && (
  27. <img
  28. src={`${ch.bannerUrl}=w1707-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj`}
  29. alt={`${ch.name} 배너`}
  30. className="channel-page__banner-img"
  31. />
  32. )}
  33. </div>
  34. {/* 프로필 */}
  35. <div className="channel-page__profile">
  36. {/* 썸네일 */}
  37. <a href={ch.youTubeUrl} target="_blank" rel="noopener noreferrer" className={`channel-page__avatar${ch.isLive ? ' channel-page__avatar--live' : ''}`}>
  38. {ch.thumbnailUrl ? (
  39. <img src={ch.thumbnailUrl} alt={ch.name} />
  40. ) : (
  41. <div className="channel-page__avatar-placeholder">{ch.name.charAt(0)}</div>
  42. )}
  43. {ch.isLive && <span className="channel-page__live-badge">LIVE</span>}
  44. </a>
  45. {/* 채널명 + 메타 */}
  46. <div className="channel-page__info">
  47. <h1 className="channel-page__name">
  48. <a href={ch.youTubeUrl} target="_blank" rel="noopener noreferrer">{ch.name}</a>
  49. {ch.isVerified && <span className="channel-page__verified" title="인증됨">✓</span>}
  50. </h1>
  51. <div className="channel-page__meta">
  52. {ch.handle && <span>@{ch.handle}</span>}
  53. {(ch.subscriberCount ?? 0) > 0 && <span>구독자 {formatCount(ch.subscriberCount)}명</span>}
  54. {(ch.videoCount ?? 0) > 0 && <span>동영상 {ch.videoCount.toLocaleString()}개</span>}
  55. </div>
  56. {/* 데스크톱 전용: 메타 아래 인라인 */}
  57. <div className="channel-page__buttons channel-page__buttons--desktop">
  58. <a href={ch.youTubeUrl} target="_blank" rel="noopener noreferrer" className="channel-page__subscribe-btn">구독</a>
  59. <Link href={`/donation/${ch.channelSID}`} className="channel-page__donate-btn">후원하기</Link>
  60. </div>
  61. </div>
  62. </div>
  63. {/* 모바일 전용: 프로필 아래 별도 줄 */}
  64. <div className="channel-page__buttons channel-page__buttons--mobile mt-3">
  65. <a href={ch.youTubeUrl} target="_blank" rel="noopener noreferrer" className="channel-page__subscribe-btn">구독</a>
  66. <Link href={`/donation/${ch.channelSID}`} className="channel-page__donate-btn">후원하기</Link>
  67. </div>
  68. {/* 라이브 상태 */}
  69. {ch.isLive && (
  70. <div className="channel-page__live">
  71. <span className="channel-page__live-dot" />
  72. <span className="channel-page__live-title">{ch.liveTitle}</span>
  73. <Link href={`/watch/${ch.channelSID}`} className="channel-page__watch-btn">방송 보러가기 →</Link>
  74. </div>
  75. )}
  76. {/* 탭 네비게이션 */}
  77. <nav className="channel-page__tabs">
  78. <span className="channel-page__tab channel-page__tab--active">소개</span>
  79. <span className="channel-page__tab">게시물</span>
  80. </nav>
  81. {/* 소개 탭 콘텐츠 */}
  82. <div className="channel-page__tab-content">
  83. {ch.description ? (
  84. <p className="channel-page__description">{ch.description}</p>
  85. ) : (
  86. <p className="channel-page__empty">채널 소개가 없습니다</p>
  87. )}
  88. </div>
  89. </div>
  90. );
  91. }